import java.io.File

/*
 * Copyright 2019-2024 JetBrains s.r.o. and contributors.
 * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
 */

private val pkg = "package kotlinx.datetime.timezones.tzData"

private fun generateByteArrayProperty(tzData: TzData, header: String, propertyName: String): String = buildString {
    append(header)
    appendLine()
    appendLine()
    appendLine("/* ${tzData.fullTzNames.joinToString(", ")} */")
    append("internal val $propertyName get() = byteArrayOf(")
    for (chunk in tzData.data.toList().chunked(16)) {
        appendLine()
        append("    ")
        val chunkText = chunk.joinToString {
            it.toString().padStart(4, ' ')
        } + ","
        append(chunkText)
    }
    appendLine()
    append(")")
}

private class TzData(val data: ByteArray, val fullTzNames: MutableList<String>)
private fun loadTzBinaries(
    zoneInfo: File,
    currentName: String,
    result: MutableList<TzData>
) {
    val zoneName = if (currentName.isEmpty()) zoneInfo.name else "$currentName/${zoneInfo.name}"
    if (zoneInfo.isDirectory) {
        zoneInfo.listFiles()?.forEach {
            loadTzBinaries(it, zoneName, result)
        }
    } else {
        val bytes = zoneInfo.readBytes()
        val foundTzData = result.firstOrNull { it.data.contentEquals(bytes) }
        val tzData: TzData
        if (foundTzData != null) {
            tzData = foundTzData
        } else {
            tzData = TzData(bytes, mutableListOf())
            result.add(tzData)
        }

        tzData.fullTzNames.add(zoneName)
    }
}

fun generateZoneInfosResources(zoneInfoDir: File, outputDir: File, version: String) {
    val header = buildString {
        appendLine()
        append("/* AUTOGENERATED FROM ZONE INFO DATABASE v.$version */")
        appendLine()
        appendLine()
        append(pkg)
    }

    val loadedZones = mutableListOf<TzData>()
    zoneInfoDir.listFiles()?.forEach { file ->
        loadTzBinaries(file, "", loadedZones)
    }

    val zoneDataByNameBody = StringBuilder()
    val getTimeZonesBody = StringBuilder()
    loadedZones.forEachIndexed { id, tzData ->
        val tzDataName = "tzData$id"
        val data = generateByteArrayProperty(tzData, header, tzDataName)
        File(outputDir, "$tzDataName.kt").writeText(data)
        tzData.fullTzNames.forEach { name ->
            zoneDataByNameBody.appendLine("    \"$name\" -> $tzDataName")
            getTimeZonesBody.appendLine("    \"$name\",")
        }
    }

    val content = buildString {
        append(header)
        appendLine()
        appendLine()
        appendLine("internal fun zoneDataByName(name: String): ByteArray = when(name) {")
        append(zoneDataByNameBody)
        appendLine()
        append("    else -> throw kotlinx.datetime.IllegalTimeZoneException(\"Invalid timezone name\")")
        appendLine()
        append("}")
        appendLine()
        appendLine()
        append("internal val timeZones: Set<String> by lazy { setOf(")
        appendLine()
        append(getTimeZonesBody)
        appendLine()
        append(")")
        append("}")
    }

    File(outputDir, "tzData.kt").writeText(content)
}